package com.naiterui.ehp.bs.patient.service.impl;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.alibaba.fastjson.JSON;
import com.naiterui.common.redis.RedisUtil;
import com.naiterui.common.repository.page.CustomizedPageConvertor;
import com.naiterui.common.repository.page.PageVO;
import com.naiterui.ehp.bp.bo.consult.ConsultSessionListBo;
import com.naiterui.ehp.bp.bo.consult.VideoConsultBO;
import com.naiterui.ehp.bp.bo.emr.InquireCaseBO;
import com.naiterui.ehp.bp.bo.patient.MsgMaPublishParamBO;
import com.naiterui.ehp.bp.constants.CommonConstant;
import com.naiterui.ehp.bp.constants.CommonConstant.ConsultType;
import com.naiterui.ehp.bp.constants.MessageTypeEnum;
import com.naiterui.ehp.bp.constants.OrderSnType;
import com.naiterui.ehp.bp.constants.PayStatus;
import com.naiterui.ehp.bp.domain.ConsultBill;
import com.naiterui.ehp.bp.domain.ConsultSession;
import com.naiterui.ehp.bp.domain.Department;
import com.naiterui.ehp.bp.domain.Doctor;
import com.naiterui.ehp.bp.domain.DrChargeConfig;
import com.naiterui.ehp.bp.domain.DrMessageConsultOrder;
import com.naiterui.ehp.bp.domain.Hospital;
import com.naiterui.ehp.bp.domain.Patient;
import com.naiterui.ehp.bp.support.exception.BusinessException;
import com.naiterui.ehp.bp.support.exception.ResponseVO;
import com.naiterui.ehp.bp.utils.date.DateUtil;
import com.naiterui.ehp.bp.utils.net.RestHttpUtil;
import com.naiterui.ehp.bp.utils.payment.OrderUtils;
import com.naiterui.ehp.bp.utils.payment.bo.RefundRequestBO;
import com.naiterui.ehp.bp.utils.payment.bo.UnifiedOrderRequestBO;
import com.naiterui.ehp.bp.utils.payment.bo.UnifiedOrderResultBO;
import com.naiterui.ehp.bs.patient.bo.SendPatientBillBO;
import com.naiterui.ehp.bs.patient.constants.CacheConstant;
import com.naiterui.ehp.bs.patient.dao.IDoctorDao;
import com.naiterui.ehp.bs.patient.dao.IDrMessageConsultChargeDao;
import com.naiterui.ehp.bs.patient.dao.IDrMessageConsultOrderDao;
import com.naiterui.ehp.bs.patient.dao.IHospitalDao;
import com.naiterui.ehp.bs.patient.dao.IPatientDao;
import com.naiterui.ehp.bs.patient.dao.mongo.ApMessageDao;
import com.naiterui.ehp.bs.patient.domain.WeixinInfo;
import com.naiterui.ehp.bs.patient.exception.ExceptionCodes;
import com.naiterui.ehp.bs.patient.feign.EmrFeginClient;
import com.naiterui.ehp.bs.patient.feign.VideoConsultFeignClient;
import com.naiterui.ehp.bs.patient.repository.IApConsultSessionRepository;
import com.naiterui.ehp.bs.patient.repository.IApDepartmentRepository;
import com.naiterui.ehp.bs.patient.repository.IWeixinInfoRepository;
import com.naiterui.ehp.bs.patient.rpc.im.IConsultSessionRemoter;
import com.naiterui.ehp.bs.patient.service.IConsultBillService;
import com.naiterui.ehp.bs.patient.service.IConsultSessionService;
import com.naiterui.ehp.bs.patient.service.function.IFunctionService;
import com.naiterui.ehp.bs.patient.service.message.IMessageSendService;
import com.naiterui.ehp.bs.patient.utils.ConfigKeyUtil;
import com.naiterui.ehp.bs.patient.utils.ImgUtil;
import com.naiterui.ehp.bs.patient.utils.SMSUtil;
import com.naiterui.ehp.bs.patient.utils.SMSUtil.Template;
import com.naiterui.ehp.bs.patient.utils.TitleUtil;
import com.naiterui.ehp.bs.patient.utils.cache.BaseParamCacheUtil;
import com.naiterui.ehp.bs.patient.utils.constants.PatientConstant;
import com.naiterui.ehp.bs.patient.utils.exception.PatientBusinessException;
import com.naiterui.ehp.bs.patient.vo.ConsultDetail;
import com.naiterui.ehp.bs.patient.vo.consult.ConsultVO;
import com.naiterui.ehp.bs.patient.vo.consult.Message;
import com.naiterui.ehp.bs.patient.vo.doctor.DoctorVO;
import com.naiterui.ehp.bs.patient.vo.parameters.DepartmentVO;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import lombok.AllArgsConstructor;

/**
 * 问诊会话业务接口实现
 *
 * @author Amber
 * @since 1.0.0
 */
@Service
@AllArgsConstructor
public class ApConsultSessionServiceImpl implements IConsultSessionService {

    private static final Logger LOGGER = LoggerFactory.getLogger(ApConsultSessionServiceImpl.class);

    /**
     * 患者域名
     */
    private static final String PATIENT_URL = ConfigKeyUtil.getString("patient.url");
    /**
     * 图文咨询域名
     */
    private static final String CONSULT_URL = ConfigKeyUtil.getString("consult.url");

    private final IDoctorDao doctorDao;
    private final IPatientDao patientDao;
    private final IFunctionService functionService;
    private final IHospitalDao hospitalDao;
    private final EmrFeginClient emrFeginClient;
    private final IApDepartmentRepository departmentRepository;
    private final IApConsultSessionRepository consultSessionRepository;
    private final IConsultBillService consultBillService;
    private final IDrMessageConsultOrderDao drMessageConsultOrderDao;
    private final IDrMessageConsultChargeDao drMessageConsultChargeDao;
    private final IMessageSendService messageSendService;
    private final VideoConsultFeignClient consultFeignClient;
    private final ApMessageDao messageDao;
    private final IWeixinInfoRepository weixinInfoRepository;
    private final IConsultSessionRemoter consultSessionRemoter;

    @Override
    public PageVO<ConsultVO> getMyConsultSessions(Long patientId, int page, int num) throws BusinessException {

        // 用户校验
        Patient patient = patientDao.get(patientId);
        // 患者不存在
        if (patient == null) {
            LOGGER.error("getConsultSessions Error ; patient not exist : patientId {}", patientId);
            throw new PatientBusinessException(ExceptionCodes.PATIENT_NOT_EXIST);
        }
        // 患者账号被禁用
        if (patient.getAccountStatus() != 0) {
            LOGGER.error("getConsultSessions Error ; patient account status wrong : patientId {}", patientId);
            throw new PatientBusinessException(ExceptionCodes.PATIENT_ACCOUNT_ERROR);
        }
        // 返回结果分页信息VO
        PageVO<ConsultVO> consultPageVO = new PageVO<>();

        // 分页获取问诊会话信息实体，不含进行中的问诊
        Page<ConsultSession> sessionPage = consultSessionRepository.findByPatientId(patientId, PageRequest.of(page, num));

        // 会话实体数据集合
        List<ConsultSession> sessions = sessionPage.getContent();
        // 问诊VO列表结果集
        List<ConsultVO> consultVOs = new ArrayList<>();
        // 问诊记录账单id
        List<Long> billIds = new ArrayList<>();

        for (ConsultSession session : sessions) {

            // 会话已产生账单
            if (session.getBillId() != null) {
                billIds.add(session.getBillId());
            }

            // 问诊信息VO
            ConsultVO consultVO = new ConsultVO();
            consultVO.setConsultId(session.getId());
            consultVO.setBillId(session.getBillId());
            consultVO.setCommentStatus(session.getCommnetStatus());
            consultVO.setHaveAppraise(session.getCommnetStatus() == ConsultSession.HAVE_COMMENT);

            // 医生信息
            Long doctorId = session.getDoctorId();
            // 医生存在时，设置基本信息到相应会话信息
            if (doctorId != null) {
                // 初始化医生数据
                Doctor doctor = doctorDao.get(doctorId);
                if (doctor != null) {
                    DoctorVO doctorVO = new DoctorVO();
                    doctorVO.setName(doctor.getName());// 姓名

                    // 科室信息
                    if (null != doctor.getDepartmentId() && doctor.getDepartmentId() != 0) {// 存在科室，且非自定义
                        // 缓存中获取科室信息
                        DepartmentVO departmentVO = BaseParamCacheUtil.getDepartmentFromRedis(doctor.getDepartmentId());
                        if (departmentVO != null) {// 缓存中存在科室
                            doctorVO.setDepartment(departmentVO.getName());// 科室名
                        } else {// 缓存没有科室信息，数据库读取
                            Department department = departmentRepository.get(doctor.getDepartmentId());
                            doctorVO.setDepartment(department == null ? "" : department.getName());// 科室名，查不到科室是设置""
                        }
                    } else {// 否则按自定义科室名显示
                        doctorVO.setDepartment(doctor.getCustomDepartment() == null ? "" : doctor.getCustomDepartment());
                    }

                    // 获取医生医院信息
                    if (null != doctor.getHospitalId() && doctor.getHospitalId() != 0) {// 存在医院id，且非自定义
                        String hpName = doctorDao.getHospitalName(doctor.getHospitalId());// 数据库获取医院名
                        doctorVO.setHospital(hpName == null ? "" : hpName);// 设置医院名，未查询到时设置为""
                    } else {// 自定义医院
                        doctorVO.setHospital(doctor.getHospitalName());// 设置医院名
                    }

                    // 认证状态
                    doctorVO.setAuthStatus(doctor.getStatus());

                    consultVO.setDoctor(doctorVO);
                }
            }

            // 基础信息
            consultVO.setAmount(session.getCost());// 问诊费用
            consultVO.setHaveRecom(session.getRecomDrugFlag() == ConsultSession.HAVE_RECOM);// 是否有推荐用药

            consultVO.setStartTime(DateUtil.formatDate(session.getStartTime(), DateUtil.DATE_TIME_FORMAT));// 开始时间
            consultVO.setEndTime(DateUtil.formatDate(session.getEndTime(), DateUtil.DATE_TIME_FORMAT));// 结束时间

            consultVOs.add(consultVO);

        }

        // TODO: 2017/6/9 王泽浩 这里需要看以下逻辑是否在使用，和前端确认

        // 批量获取问诊账单支付状态
        Map<Long, ConsultBill> payStatus = consultBillService.getConsultBillPayStatus(billIds);
        for (ConsultVO consult : consultVOs) {
            ConsultBill bill = payStatus.get(consult.getBillId());
            if (bill == null) {
                continue;
            }
            consult.setHavePay(bill.getPayStatus() != ConsultBill.PAY_STATUS_NO_PAID);
            // 是否以支付
            if (bill.getPayStatus() == ConsultBill.PAY_STATUS_NO_PAID) {
                // 问诊未支付状态
                consult.setStatus(PatientConstant.CONSULT_STATUS_MAP.get(bill.getPayStatus()));
            } else {
                // 问诊未状态
                consult.setStatus(PatientConstant.CONSULT_COMMENT_STATUS_MAP.get(consult.getCommentStatus()));
            }

            if (bill.getResourceType() == ConsultBill.RESOURCE_TYPE_NORMAL || bill.getResourceType() == ConsultBill.RESOURCE_TYPE_FREE) { //
                // 正常的需要支付，其他为医生不收费或医生主动发起
                consult.setNeedPay(true);// 需要支付
            } else {
                consult.setNeedPay(false);// 不需要支付
            }
        }

        // 处理分页信息
        //consultPageVO = CustomizedPageConvertor.pageConvert(sessionPage, consultVOs);
        return PageVO.newBuilder()
                .pageNo(page)
                .pageSize(num)
                .totalCount((int) sessionPage.getTotalElements())
                .result(consultVOs).build();
    }

    @Override
    public ConsultSession getConsultSessionById(Long consultId) {
        return consultSessionRepository.findFirstByBillId(consultId);
    }

    @Override
    public ConsultSession getLastConsultByPatientAndDoctor(Long patientId, Long doctorId) {
        return consultSessionRepository.findFirstByPatientIdAndDoctorIdOrderByStartTimeDesc(patientId, doctorId);
    }

    private void handleDoctorMessage(PageVO<ConsultDetail> resultPageVO) {

        if (null == resultPageVO || null == resultPageVO.getResult() || resultPageVO.getResult().isEmpty()) {
            return;
        }
        List<ConsultDetail> resultResult = resultPageVO.getResult();

        // 构建参数
        Set<Long> doctorIdSet = new HashSet<>();
        for (ConsultDetail consultDetail : resultResult) {
            Long doctorId = ConsultDetail.CONSULT_SOURCE_PATIENT == consultDetail.getRelation() ? consultDetail.getToId() : consultDetail.getFromId();
            doctorIdSet.add(doctorId);
        }
        Map<Long, Doctor> doctorMap = doctorDao.getDoctorMap(doctorIdSet);
        Set<Integer> hospitalIdSet = new HashSet<>();
        for (Doctor tempDoctor : doctorMap.values()) {
            hospitalIdSet.add(tempDoctor.getHospitalId());
        }
        Map<Integer, Hospital> hospitalMap = hospitalDao.getHospitalMap(hospitalIdSet);

        // Consult医生信息封装
        for (ConsultDetail consultDetail : resultResult) {
            Long doctorId = ConsultDetail.CONSULT_SOURCE_PATIENT == consultDetail.getRelation() ? consultDetail.getToId() : consultDetail.getFromId();

            Doctor doctor = doctorMap.get(doctorId);
            if (null == doctor) {
                continue;
            }

            String doctorTitle = null == doctor.getTitleId() ? "" : "·" + TitleUtil.getTitle(doctor.getTitleId());
            consultDetail.setName(doctor.getName());
            consultDetail.setHeadUrl(ImgUtil.getDoctorPhoto(doctor.getHeadUrl()));
            consultDetail.setStatus(doctor.getStatus());

            if (null == doctor.getDepartmentId() || doctor.getDepartmentId() <= 0) {
                consultDetail.setDepartment(StringUtils.isBlank(doctor.getCustomDepartment()) ? "" : doctor.getCustomDepartment() + doctorTitle);
            } else {
                DepartmentVO tempDepartment = BaseParamCacheUtil.getDepartmentFromRedis(doctor.getDepartmentId());
                consultDetail.setDepartment(null == tempDepartment ? "" : tempDepartment.getName() + doctorTitle);
            }

            if (null == doctor.getHospitalId() || doctor.getHospitalId() <= 0) {
                consultDetail.setHospital(StringUtils.isBlank(doctor.getHospitalName()) ? "" : doctor.getHospitalName());
            } else {
                Hospital tempHospital = hospitalMap.get(doctor.getHospitalId());
                consultDetail.setHospital(null == tempHospital ? "" : tempHospital.getName());
            }
        }
    }

    /**
     * 参数转换
     * ConsultSessionServiceImpl.convertValue()
     *
     * @param source
     * @return
     * @Author Gaoll
     * @Date 2016年8月12日
     * @since HZD-1.0.0
     */
    private String convertValue(Object source) {
        return null == source ? "" : source.toString();
    }

    @Override
    public Map<String, Object> getPatientConsultFee(Long patientId, Long doctorId) throws BusinessException {

        Doctor doctor = doctorDao.get(doctorId);
        // 患者不存在
        if (doctor == null) {
            LOGGER.error("getPatientConsultPayInfo Error ; doctor not exist : doctorId {}", doctorId);
            throw new PatientBusinessException(ExceptionCodes.DOCTOR_NOT_EXIST);
        }

        // 医生被禁用
        if (1 == doctor.getAccountStatus()) {
            LOGGER.error("getPatientConsultPayInfo Error ; doctor account error : doctorId {}", doctorId);
            throw new PatientBusinessException(ExceptionCodes.DOCTOR_ACCOUNT_ERROR);
        }
        // 校验医患关系
        functionService.isdDleteDoctorPatientRelation(doctorId, patientId);
        Map<String, Object> consultPayInfoMap = new HashMap<>();
        consultPayInfoMap.put("doctorId", doctorId);
        int doctorStatus = doctor.getStatus();
        if (Doctor.AUTH_STATUS_SUCCESS == doctorStatus) {
            // 审核通过--表示已认证
            consultPayInfoMap.put("drStatus", Doctor.AUTH_STATUS_SUCCESS);
            consultPayInfoMap.put("drHeaderUrl", ImgUtil.getDoctorPhoto(doctor.getHeadUrl()));
        } else {
            // 其他状态都置为0，表示未认证
            consultPayInfoMap.put("drStatus", 0);
            consultPayInfoMap.put("drHeaderUrl", "");
        }
        consultPayInfoMap.put("drName", doctor.getName());
        consultPayInfoMap.put("patientId", patientId);
        Integer consultCost = drMessageConsultChargeDao.getChargeById(doctorId, patientId);
        consultPayInfoMap.put("price", consultCost);
        Integer videoConsultCost = drMessageConsultChargeDao.getChargeById(doctorId, patientId, DrChargeConfig.TYPE_VIDEO);
        consultPayInfoMap.put("videoCost", videoConsultCost);
        return consultPayInfoMap;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public UnifiedOrderResultBO addPatientConsultPayInfo(Long patientId, Long doctorId, String ip, Integer origin) throws Exception {
        LOGGER.info("图文咨询支付请求参数: patientId {}, doctorId {}", patientId, doctorId);
        // 判断医患关系是否删除
        functionService.isdDleteDoctorPatientRelation(doctorId, patientId);
        // 查询医生的设置的价格
        Integer consultCost = drMessageConsultChargeDao.getChargeById(doctorId, patientId);
        if (0 == consultCost) {
            // 如果价格为零,会话无需支付
            throw new BusinessException(ExceptionCodes.SESSION_STATUS_NO_PAY);
        }

        ConsultSession lastConsultSession = getLastConsultByPatientAndDoctor(patientId, doctorId);
        if (lastConsultSession != null && lastConsultSession.getEndTime() == null) {
            // 如果有进行中的会话，会话无需支付
            throw new BusinessException(ExceptionCodes.SESSION_STATUS_NO_PAY);
        }

        DrMessageConsultOrder consultOrder = drMessageConsultOrderDao.findByPatientAndDoctorAndPayStatus(patientId, doctorId,
                PayStatus.WAIT_PAY.getValue());

        if (consultOrder == null) {
            LOGGER.info("支付请求图文咨询订单号,生成新的订单,参数: patientId {}, doctorId {}", patientId, doctorId);
            // 设置唯一key，值为支付状态，待支付
            String orderSn = OrderUtils.getOrderSn(OrderSnType.MESSAGE_CONSULT_ORDERSN);
            consultOrder = new DrMessageConsultOrder();
            consultOrder.setOrderSn(orderSn);
            consultOrder.setDoctorId(doctorId);
            consultOrder.setPatientId(patientId);
            // 设置支付状态，待支付
            consultOrder.setPayStatus(PayStatus.WAIT_PAY.getValue());
        }
        consultOrder.setPrice(consultCost);
        // 保存积分
        consultOrder.setPoint(consultCost / 10);
        // 这里是订单时间，所以需要更新创建时间
        consultOrder.setCreatedAt(new Date());
        drMessageConsultOrderDao.saveOrUpdate(consultOrder);
        String orderSn = consultOrder.getOrderSn();
        Patient patient = patientDao.get(patientId);
        WeixinInfo weixinInfo = weixinInfoRepository.findByMiniOpenId(patient.getMiniOpenId());
        return OrderUtils.pay(UnifiedOrderRequestBO.builder()
                // 订单号
                .orderSn(orderSn)
                // 商品名称
                .body(orderSn)
                // 用户OPEN id
                .openId(weixinInfo.getMiniOpenId())
                // 用户ip
                .ip(ip)
                // 订单金额
                .totalFee(consultCost)
                // 订单来源
                .origin(origin)
                // 业务通知地址
                .notifyBusinessUrl(PATIENT_URL + "/inner/notify/pay")
                // 订单类型
                .orderType(OrderSnType.MESSAGE_CONSULT_ORDERSN).build());
    }

    @Override
    @Transactional
    public Map<String, Object> checkMessageConsultStatus(Long patientId, Long doctorId) throws BusinessException {
        // 判断关系
        functionService.isdDleteDoctorPatientRelation(doctorId, patientId);

        Map<String, Object> statusMap = new HashMap<>();
        Doctor doctor = doctorDao.get(doctorId);
        statusMap.put("doctorName", doctor.getName());
        Integer consultCost = drMessageConsultChargeDao.getChargeById(doctorId, patientId);
        statusMap.put("consultCost", consultCost);
        Integer charge = drMessageConsultChargeDao.getChargeById(doctorId, patientId);
        if (charge < 1) {
            // 0表示免费
            statusMap.put("flag", 0);
            return statusMap;
        }

        // 查询支付成功并且会话开启中的订单，有直接返回true，表示可以聊
        DrMessageConsultOrder drMessageConsultOrder = drMessageConsultOrderDao
                .findOrderByPatientAndDoctorAndConsultSessionStatus(patientId, doctorId, DrMessageConsultOrder.CONSULT_SESSION_STATUS_BEGIN);
        if (drMessageConsultOrder != null) {
            statusMap.put("flag", PayStatus.PAY_SUCCESS.getValue());
            return statusMap;
        }

        // 如果没有支付成功并且会话开启的订单，则查看是否有待支付的订单
        DrMessageConsultOrder drConsultOrder = drMessageConsultOrderDao.findByPatientAndDoctorAndPayStatus(patientId, doctorId,
                PayStatus.WAIT_PAY.getValue());
        if (drConsultOrder == null) {
            // 如果没有待支付的订单,看是否又支付中的订单
            DrMessageConsultOrder consultOrder = drMessageConsultOrderDao
                    .findByPatientAndDoctorAndPayStatus(patientId, doctorId, PayStatus.PAY_NOT_CONFIRMED.getValue());
            // 如果没有支付中订单，表示没有发起支付
            if (consultOrder == null) {
                statusMap.put("flag", PayStatus.WAIT_PAY.getValue());
            } else {
                // 有支付中的订单，表示正在支付
                statusMap.put("flag", PayStatus.PAY_NOT_CONFIRMED.getValue());
            }
            return statusMap;
        }

        try {
            // 调用微信接口查询支付状态
            Integer payStatus = OrderUtils.query(drConsultOrder.getOrderSn());
            if (PayStatus.WAIT_PAY.getValue() == payStatus) {
                // 主动查询微信结果，未支付
                statusMap.put("flag", PayStatus.WAIT_PAY.getValue());
            } else if (PayStatus.PAY_FAIL.getValue() == payStatus) {
                // 主动查询微信结果，支付失败，表示支付失败
                statusMap.put("flag", PayStatus.PAY_FAIL.getValue());
            } else if (PayStatus.PAY_SUCCESS.getValue() == payStatus) {
                // 主动查询微信结果，支付成功，表示支付中
                statusMap.put("flag", PayStatus.PAY_NOT_CONFIRMED.getValue());
            } else {
                // 主动查询微信结果，其他情况默认表示支付失败，DBYZ-5604 临时解决方案 by Amber
                statusMap.put("flag", PayStatus.PAY_FAIL.getValue());
            }
        } catch (Exception e) {
            LOGGER.error("校验图文咨询可否聊天接口主动查询微信支付结果失败,orderSn {}", drConsultOrder.getOrderSn(), e);
            // 异常认为是待支付顶大
            statusMap.put("flag", PayStatus.WAIT_PAY.getValue());
        }

        LOGGER.info("校验图文咨询可否聊天结果:{}", JSONUtil.toJsonStr(statusMap));
        return statusMap;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateNotifyPay(String orderSn, Integer payStatus) throws BusinessException {
        LOGGER.info("图文咨询支付微信回调开始。orderSn {},status {} ", orderSn, payStatus);
        DrMessageConsultOrder drMessageConsultOrder = drMessageConsultOrderDao.findMessageConsultOrderByOrderSn(orderSn);
        // 更改订单状态为回调状态
        drMessageConsultOrder.setPayStatus(payStatus);
        drMessageConsultOrder.setChangedAt(new Date());
        drMessageConsultOrder.setChangedBy("WX");
        drMessageConsultOrderDao.update(drMessageConsultOrder);
        if (PayStatus.PAY_SUCCESS.getValue() != payStatus) {
            return;
        }
        Long patientId = drMessageConsultOrder.getPatientId();
        Long doctorId = drMessageConsultOrder.getDoctorId();
        Doctor doctor = doctorDao.get(drMessageConsultOrder.getDoctorId());
        Patient patient = patientDao.get(patientId);
        Integer price = drMessageConsultOrder.getPrice();
        String patientName = StringUtils.isNotEmpty(patient.getName()) ? patient.getName() : patient.getNickName();

        String prepareValue = RedisUtil.hashOps().hget(CacheConstant.getSessionPrepareKey(), CacheConstant.getSessionKey(doctorId, patientId));

        //TODO 此处发送病历暂时异步，支付回调非主业务逻辑需要拆离
        Integer type = 0;
        String recordId = null;
        Long inquirerId;
        if (StringUtils.isNotBlank(prepareValue)) {
            String[] prepareParam = prepareValue.split("_");
            type = Integer.parseInt(prepareParam[0]);
            recordId = prepareParam[1];
        }
        InquireCaseBO caseBO;
        switch (type) {
            case 1:
                caseBO = emrFeginClient.getInquirerCase(recordId);
                inquirerId = caseBO.getInquirerId();
                break;
            case 2:
                caseBO = emrFeginClient.getInquirerDiesase(recordId);
                inquirerId = caseBO.getInquirerId();
                break;
            default:
                inquirerId = 0L;
        }
        // 付费会话就诊人ID
        RedisUtil.hashOps().hset(CommonConstant.CONSULT_PATIENT_AIM_INQUIRER, String.join("_", doctorId.toString(), patientId.toString()), inquirerId.toString());
        //患者触发的支付后会话，默认等同于同意免责协议
        RedisUtil.hashOps().hset(CommonConstant.CONSULT_DISCLAIMER_ACTIVE, patientId.toString() + "_" + doctorId.toString(), Boolean.TRUE.toString());

        // 开启会话
        LOGGER.info("调用IM接口开启会话开始，参数:patientId {}, doctorId {}", patientId, doctorId);
        String url = ConfigKeyUtil.getString(ConfigKeyUtil.PATIENT_MESSAGE_CONSULT_TO_IM);
        Map<String, String> params = new HashMap<>();
        params.put("patientId", patientId + "");
        params.put("doctorId", doctorId + "");
        params.put("payAmount", price + "");
        params.put("patientName", patientName);
        params.put("doctorName", doctor.getName());
        params.put("orderSn", drMessageConsultOrder.getOrderSn());
        Map<String, String> data = RestHttpUtil.post()
                .url(url)
                .paramMap(params)
                .executeData(new ParameterizedTypeReference<ResponseVO<Map<String, String>>>() {
                });
        RedisUtil.valueOps().set(CommonConstant.PT_MESSAGE_CONSULT + patientId + "_" + doctorId, PayStatus.PAY_SUCCESS.getValue());
        String sessionId = data.get("sessionId");
        Long consultSessionId = Long.parseLong(data.get("consultSessionId"));

        // 若异常，未获取sessionId、consultSessionId则尝试redis获取
        if (StringUtils.isBlank(sessionId) || consultSessionId == null) {
            sessionId = RedisUtil.hashOps().hget(CommonConstant.PAY_CONSULT_SESSION_KEY + drMessageConsultOrder.getOrderSn(), "sessionId");
            String consultIdStr = RedisUtil.hashOps().hget(CommonConstant.PAY_CONSULT_SESSION_KEY + drMessageConsultOrder.getOrderSn(),
                    "consultSessionId");
            consultSessionId = StringUtils.isNumeric(consultIdStr) ? Long.valueOf(consultIdStr) : null;
            LOGGER.warn("调用IM接口开启会话 IM接口异常，尝试Redis获取 ；sessionId={}，consultSessionId={}", sessionId, consultSessionId);
        }

        LOGGER.info("调用IM接口开启会话 INFO ; sessionId:{},consultSessionId:{},orderSn:{}", sessionId, consultSessionId, drMessageConsultOrder.getOrderSn());
        drMessageConsultOrder.setSessionId(sessionId);
        drMessageConsultOrder.setConsultSessionId(consultSessionId);
        drMessageConsultOrder.setConsultSessionStatus(DrMessageConsultOrder.CONSULT_SESSION_STATUS_BEGIN);

        // 保存会话id，会话状态
        drMessageConsultOrderDao.update(drMessageConsultOrder);

        RedisUtil.keyOps().expire(CommonConstant.PAY_CONSULT_SESSION_KEY + drMessageConsultOrder.getOrderSn(), 60 * 60 * 24);

        // 发送短信
        String dateStr = DateUtil.formatDate(new Date(), "MM-dd HH:mm");
        // String content = String.format(StringConstant.SMS_MESSAGE_CONSULT_POINT_NOTICE, patientName, dateStr, price / 100);
        Template tmpl = Template.SMS_MESSAGE_CONSULT_POINT_NOTICE_TEMPLATE;
        SMSUtil.asyncSendSMS(doctor.getPhone(), tmpl, patientName, dateStr, price / 100);

        // 发送push
        try {
            //TODO 此处发送病历暂时异步，支付回调非主业务逻辑需要拆离
            if (ObjectUtil.isAllNotEmpty(type, recordId)) {
                messageSendService.sendRecord(patientId, inquirerId, doctorId, type, recordId, patient.getOpenId(), doctor.getName(), null);
            }
            RedisUtil.hashOps().hdel(CacheConstant.getSessionPrepareKey(), CacheConstant.getSessionKey(doctorId, patientId));

            // AppPushUtil.sendDrPush(ConfigKeyUtil.getString("push.message.server"), doctorId, content, AppJumpProtocol.DR_HOME);
        } catch (Exception e) {
            LOGGER.error("调用IM接口开启会话 推送PUSH ERROR ", e);
        }

        LOGGER.info("图文咨询支付微信回调结束。orderSn {},payStatus {} ", orderSn, payStatus);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateConsultInvalid(Long patientId, Long doctorId, String sessionId) throws Exception {
        // 2018/3/19 王泽浩 这里要先判断是不是图文付费业务，如果没有，那么就去查询是不是咨询活动业务 然后发起退款
        DrMessageConsultOrder drMessageConsultOrder = drMessageConsultOrderDao.getBySessionId(patientId, doctorId, sessionId);
        //ConsultActivityOrder consultActivityOrder = this.consultActivityOrderDao.findBySessionId(sessionId);
        String orderSn;
        int price;
        if (drMessageConsultOrder != null) {
            orderSn = drMessageConsultOrder.getOrderSn();
            price = drMessageConsultOrder.getPrice();
            refund(PATIENT_URL, orderSn, price);
        }
        /*else if (consultActivityOrder != null) {
            orderSn = consultActivityOrder.getOrderSn();
            price = consultActivityOrder.getPrice();
            this.refund(PATIENT_URL, orderSn, price);
        }
        else {
            // 判断找医生，线上诊室订单
            ConsultRoomOrderBO consultRoomOrderBO = this.remoter.getConsultRoomOrderBySessionId(sessionId);
            if (consultRoomOrderBO != null) {
                orderSn = consultRoomOrderBO.getOrderSn();
                price = consultRoomOrderBO.getPrice();
                this.refund(CONSULT_URL, orderSn, price);
            } else {
                LOGGER.info("免费会话，不需要更新状态 patientId:{} doctorId:{} sessionId:{}", patientId, doctorId, sessionId);
                return;
            }
        }*/
    }

    // 调用退款接口，不需要等待返回，会有重试机制去判断
    private void refund(String url, String orderSn, int price) throws Exception {
        LOGGER.info("调用退款接口 orderSn:{}", orderSn);
        OrderUtils.refund(RefundRequestBO.builder()
                // 订单号
                .orderSn(orderSn)
                .refundOrderSn(orderSn + "RE")
                // 订单金额
                .totalFee(price)
                // 退款金额
                .refundFee(price)
                // 业务回调地址
                .notifyBusinessUrl(url + "/inner/notify/refund").build());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateNoticePatient(boolean success, String orderSn) {
        LOGGER.info("退款业务，是否成功：{} 订单号：{}", success, orderSn);
        DrMessageConsultOrder messageConsultOrder = drMessageConsultOrderDao.findMessageConsultOrderByOrderSn(orderSn);
        if (messageConsultOrder == null || messageConsultOrder.getPayStatus() != PayStatus.PAY_SUCCESS.getValue()) {
            LOGGER.error("没有查询到图文收费订单或者订单状态不是支付成功状态，不处理退款业务。订单号:{}", orderSn);
            return;
        }
        // 退款
        messageConsultOrder.setPayStatus(PayStatus.REFUND.getValue());
        drMessageConsultOrderDao.update(messageConsultOrder);
        Long doctorId = messageConsultOrder.getDoctorId();
        Doctor doctor = doctorDao.get(doctorId);
        Long patientId = messageConsultOrder.getPatientId();
        Patient patient = patientDao.get(patientId);
        // 给患者退费
        sendPatientBill(SendPatientBillBO.builder()
                .success(success)
                .sessionId(messageConsultOrder.getSessionId())
                .doctorId(doctorId)
                .doctorName(doctor.getName())
                .patientId(patientId)
                .patientOpenId(patient.getOpenId())
                .price(messageConsultOrder.getPrice())
                .orderDate(messageConsultOrder.getCreatedAt())
                .orderSn(orderSn)
                .build());
    }

    /**
     * 退费给患者推送通知
     *
     * @param sendPatientBillBO 通知信息
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void sendPatientBill(SendPatientBillBO sendPatientBillBO) {
        LOGGER.debug("退费给患者推送通知 {}", sendPatientBillBO);

        // 订单时间
//        String orderDate = DateUtil.formatDate(sendPatientBillBO.getOrderDate(), "MM-dd HH:mm");
        // 订单金额
        Integer price = sendPatientBillBO.getPrice();
//        String content = sendPatientBillBO.getSuccess()
//                ? String.format(ConfigKeyUtil.getString("retry.refund.order.success"), orderDate, price / 100) + "\n----------\n" +
//                webAuthorizeApiServiceImpl.getWebAuthorizeUrlSilent(
//                        ConfigUtil.getString("wechat.redirect.common.url"),
//                        PatientJumperEnum.DOCTOR_DETAIL.encodeWxParamStr(sendPatientBillBO.getDoctorId().toString()),
//                        "再次咨询" + sendPatientBillBO.getDoctorName() + "医生")
//                : String.format(ConfigKeyUtil.getString("retry.refund.order.fail"), orderDate, price / 100);
//        // 会话结束，生成账单，触发患者端提示
//        customMsgApiService.sendSystemMsg(sendPatientBillBO.getPatientOpenId(), content);
        Map<String, Object> paramsMap = new HashMap<>(6);
        paramsMap.put("orderNumber", sendPatientBillBO.getOrderSn());
        paramsMap.put("productName", "图文问诊");
        paramsMap.put("refundAmount", price / 100);
        paramsMap.put("refundCause", "医生未回复");
        messageSendService.publishMaMessage(MsgMaPublishParamBO.builder()
                .doctorId(sendPatientBillBO.getDoctorId())
                .patientId(sendPatientBillBO.getPatientId())
                .msgType(MsgMaPublishParamBO.MsgType.REFUND_NOTICE)
                .content(JSONUtil.toJsonStr(paramsMap))
                .build());


        /*
        Map<String, String> params = new HashMap<>();
        params.put("type", "bill");
        params.put("consultId", sendPatientBillBO.getSessionId());
        params.put("openId", sendPatientBillBO.getPatientOpenId());
        params.put("doctorName", sendPatientBillBO.getDoctorName());
        params.put("doctorId", sendPatientBillBO.getDoctorId() + "");
        params.put("patientId", sendPatientBillBO.getPatientId() + "");
        params.put("content", content);
        // 这个字段只是在图文退费的时候使用
        params.put("msgtype", 1 + "");
        try {
            String body = JsonMapper.toJson(params);
            HttpClientUtil.HttpVO resultVO = HttpClientUtil.doPost(ConfigKeyUtil.getString("weixin.patient.bill"), body);
            if (resultVO.getStatus() == HttpStatus.SC_OK) {
                LOGGER.debug("退款通知患者端成功。 params {},resultData {}", body, resultVO);
            }
        } catch (Exception e) {
            LOGGER.error("退款发送通知失败msg:{}", e.getMessage());
        }*/
    }

    @Override
    public Boolean endMessageValid(Long consultSessionId, Integer type) throws BusinessException {
        if (type != null && ConsultType.VIDEO == type) {
            VideoConsultBO videoConsultBO = consultFeignClient.getVideoConsultById(consultSessionId);
            if (videoConsultBO == null) {
                throw new BusinessException(ExceptionCodes.CONSULT_NOT_EXIST);
            }
            Date expectDate = DateUtil.addDays(videoConsultBO.getChangedAt(), 1);
            return videoConsultBO.getFinish() && expectDate != null && expectDate.before(new Date());
        }
        ConsultSession consultSession = consultSessionRepository.get(consultSessionId);
        if (consultSession == null) {
            throw new BusinessException(ExceptionCodes.CONSULT_NOT_EXIST);
        }
        //超时时间
        Date expectDate = DateUtil.addDays(consultSession.getEndTime(), 1);
        return expectDate != null && expectDate.before(new Date());
    }

    @Override
    public Map<Long, Integer> getDoctorServiceNum(Collection<Long> doctorIds) throws BusinessException {
        List<ConsultSession> list = consultSessionRepository.findByDoctorIdIn(doctorIds);
        Map<Long, Integer> map = list.stream().collect(Collectors.groupingBy(ConsultSession::getDoctorId, Collectors.summingInt(o -> 1)));
        return map;
    }

    /**
     * 获取患者图文咨询列表
     *
     * @param patientId
     * @param page
     * @param num
     * @return
     */
    @Override
    public PageVO<ConsultSessionListBo> consultSessionList(Long patientId, int page, int num) throws BusinessException {
        List<ConsultSessionListBo> consultVOs = new ArrayList<>();
        List<com.naiterui.ehp.bs.patient.vo.consult.Message> messages = this.sortedAndCompareMessage(patientId);
        if (CollectionUtils.isNotEmpty(messages)) {
            List<Long> doctorIdList = new ArrayList<>();
            for (com.naiterui.ehp.bs.patient.vo.consult.Message message : messages) {
                doctorIdList.add(message.getTo().getId());
            }
            Map<Long, Boolean> consultPayStatusMap = new HashMap<>();
            if (doctorIdList.size() > 0 && patientId != null) {
                Long[] dids = new Long[doctorIdList.size()];
                consultPayStatusMap = consultSessionRemoter.aliveMap(patientId, doctorIdList.toArray(dids));
            }
            Map<Long, Boolean> finalConsultPayStatusMap = consultPayStatusMap;
            consultVOs = messages.stream().map(s -> {
                LOGGER.info("咨询记录最新消息：patientId = {}, message = {}", patientId, JSON.toJSONString(s));
                ConsultSessionListBo sessionBo = new ConsultSessionListBo();
                sessionBo.setSessionId(s.getSession() == null ? "" : s.getSession().getSessionId());
                Long doctorId = s.getTo().getId();
                sessionBo.setDoctorId(doctorId);
                sessionBo.setPatientId(patientId);
                sessionBo.setRelation(s.getRelation());
                Doctor doctor = doctorDao.get(doctorId);
                sessionBo.setConsultTime(DateUtil.formatDate(new Date(s.getSendTime()), DateUtil.TIME_MIN_FORMAT));
                sessionBo.setConsultTimestamp(s.getSendTime());
                if (MessageTypeEnum.TEXT.getType().equals(s.getType()) || Arrays.stream(MessageTypeEnum.values()).noneMatch(t -> t.getType().equals(s.getType()))) {
                    sessionBo.setMessageContent(s.getContent() + StrUtil.EMPTY);
                } else {
                    MessageTypeEnum msgTypeEnum = MessageTypeEnum.getByType(s.getType());
                    sessionBo.setMessageContent(null == msgTypeEnum ? "" : msgTypeEnum.getViewContent());
                }
                if (doctor != null) {
                    boolean sessionAlive = finalConsultPayStatusMap.get(doctor.getId()) == null ? false : finalConsultPayStatusMap.get(doctor.getId());
                    sessionBo.setAliveMessage(sessionAlive);
                    sessionBo.setDoctorName(doctor.getName());
                    sessionBo.setPhoto(StringUtils.isBlank(doctor.getHeadUrl()) ? "" : ImgUtil.getDoctorPhoto(doctor.getHeadUrl()));
                    sessionBo.setTitle(doctor.getTitleId() == null ? "" : TitleUtil.getTitle(doctor.getTitleId()));
                    // 科室信息
                    if (null != doctor.getDepartmentId() && doctor.getDepartmentId() != 0) {
                        // 缓存中获取科室信息
                        DepartmentVO departmentVO = BaseParamCacheUtil.getDepartmentFromRedis(doctor.getDepartmentId());
                        if (departmentVO != null) {
                            sessionBo.setDepartment(departmentVO.getName());
                        } else {
                            // 缓存没有科室信息，数据库读取
                            Department department = departmentRepository.get(doctor.getDepartmentId());
                            sessionBo.setDepartment(department == null ? "" : department.getName());
                        }
                    } else {
                        // 否则按自定义科室名显示
                        sessionBo.setDepartment(doctor.getCustomDepartment() == null ? "" : doctor.getCustomDepartment());
                    }
                    // 获取医生医院信息
                    if (null != doctor.getHospitalId() && doctor.getHospitalId() != 0) {
                        // 存在医院id，且非自定义
                        String hpName = doctorDao.getHospitalName(doctor.getHospitalId());
                        // 数据库获取医院名,设置医院名，未查询到时设置为""
                        sessionBo.setHospital(hpName == null ? "" : hpName);
                    } else {
                        // 自定义医院 设置医院名
                        sessionBo.setHospital(doctor.getHospitalName());
                    }
                }
                return sessionBo;
            }).sorted(Comparator.comparing(ConsultSessionListBo::getConsultTimestamp).reversed())
                .collect(Collectors.toList());
        }

        // 返回值
        com.naiterui.common.repository.page.Page<ConsultSessionListBo> messagePage = new com.naiterui.common.repository.page.Page<>();
        messagePage.setPageNo(page);
        messagePage.setPageSize(num);
        messagePage.setTotalCount(consultVOs.size());
        int start = com.naiterui.common.repository.page.Page.getStartOfPage(messagePage.getPageNo(), messagePage.getPageSize());
        int end = start + messagePage.getPageSize();
        int toIndex = end > consultVOs.size() ? consultVOs.size() : end;
        List<ConsultSessionListBo> pageValues;
        if (start > toIndex) {
            pageValues = new ArrayList<>();
        } else {
            pageValues = consultVOs.subList(start, end > consultVOs.size() ? consultVOs.size() : end);
        }
        messagePage.setResult(pageValues);
        return CustomizedPageConvertor.pageConvert(messagePage);
    }

    private List<com.naiterui.ehp.bs.patient.vo.consult.Message> sortedAndCompareMessage(Long patientId) {
        List<com.naiterui.ehp.bs.patient.vo.consult.Message> messages = messageDao.getLastMessageByIdAndVisualType(patientId);
        if (CollectionUtils.isNotEmpty(messages)) {
            List<com.naiterui.ehp.bs.patient.vo.consult.Message> messagesTrans = messages.stream().map(m -> {
                Message message = new Message();
                BeanUtils.copyProperties(m, message);
                if (0 != m.getRelation()) {
                    //转换发送者和接收者，用于根据医生id去重
                    message.setFrom(m.getTo());
                    message.setTo(m.getFrom());
                }
                return message;
            }).collect(Collectors.toList());
            //去重
            messages = messagesTrans.stream().sorted((a1, a2) -> a2.getSendTime().compareTo(a1.getSendTime())).collect(Collectors.collectingAndThen
                    (Collectors.toCollection(() ->
                            new TreeSet<>(Comparator.comparing(s -> s.getTo().getId()))), ArrayList::new));
        }
        return messages;
    }

}
